React VS Next.js

React와 Next.js는 어떤 차이점이 있을까?

정의

React

React는 Meta가 만든 UI 라이브러리이다. React가 화면을 그리는 일에만 집중한다. 컴포넌트를 선언하고 상태가 바뀌면 화면을 다시 그려주는 것이다. 나머지는 라우팅, 데이터 패칭, 빌드, 배포 등 개발자가 직접 선택하고 조합해야한다.

Next.js

Next.js는 Vercel이 만든 React기반 풀스택 프레임워크이다. 애플리케이션의 전체구조와 규칙을 제공해 React를 렌더링 엔진으로 사용해 웹 애플리케이션에 필요한 것들을 하나의 패키지 안에 통합한다.

렌더링 방식

React (CSR)

React 앱은 기본적으로 브라우저가 서버에서 거의 빈 HTML을 받고 JavaScript 번들을 다운로드 받은 다음 JS가 실행되면서 화면이 그려진다.

이 방식의 문제는 JS가 로드될 때까지 사용자가 기다려야하고, 검색엔진 크롤러가 빈 HTML을 읽는다.

Next.js

  • SSR(Server-Side Rendering): 사용자가 페이지 요청을 할 때마다 서버에서 HTML을 생성합니다. 최신데이터를 보여줘야하는 페이지(ex:실시간 대시보드,사용자 프로필)에 적합하다.
  • SSG(Static Site Generation): 빌드 시점에 HTML을 미리 생성. 내용이 자주 바뀌지 않는 페이지(ex:블로그 글, 문서)에 적합. CDN에서 바로 서빙되기 때문에 속도가 매우 빠르다.
  • ISR(Incremental Static Regeneration): SSG의 확장으로 정적으로 생성하되 일정시간이 지나면 백그라운드에서 페이지를 다시 생성 revalidate를 설정해서 적용
  • React Server Components(RSC): 컴포넌트가 서버에서만 실행되고, 결과 HTML만 클라이언트로 보낸다. JS번들 크기가 줄어들고 서버에서 직접 DB나 파일시스템에 접근할 수 있다.

라우팅

React

React 자체에는 라우팅 기능이 없기 때문에 react-router-dom, tanstack Router 같은 외부 라이브러리를 설치하고 직접 경로를 정의해야한다.

Next.js

파일을 만들면 자동으로 라우트가 생성된다.

app/
├── page.tsx          → /
├── about/
│   └── page.tsx      → /about
├── blog/
│   ├── page.tsx      → /blog
│   └── [id]/
│       └── page.tsx  → /blog/123, /blog/456 ...
└── layout.tsx        → 모든 페이지 공통 레이아웃

loading.tsx를 만들면 로딩 UI가 자동 적용되며, error.tsx를 만들면 에러 바운더리가 자동 설정된다.

데이터 패칭

React

데이터를 가져오려면 useEffect + fetch를 조합하거나, Tanstack Query같은 라이브러리를 사용해야한다.

Next.js

서버 컴포넌트에서 async/await로 직접 데이터를 가져올 수 있다.

// app/products/page.tsx (서버 컴포넌트)
async function ProductList() {
  const products = await fetch('https://api.example.com/products')
    .then(res => res.json());

  return <ul>{products.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}

useEffect와 useState가 필요없다. 서버에서 데이터를 가져와 완성된 HTML을 보내기 때문에 사용자는 바로 콘텐츠를 본다. DB에 직접 접근하는 것도 가능하다.

API / 백엔드 기능

React는 프론트엔드이므로 별도의 백엔드 서버가 필요하다. Next.js는 같은 프로젝트 안에서 API 엔드포인트를 만들 수 있다.

// app/api/users/route.ts
import { NextResponse } from 'next/server';

export async function GET() {
  const users = await db.user.findMany();
  return NextResponse.json(users);
}

export async function POST(request: Request) {
  const body = await request.json();
  const user = await db.user.create({ data: body });
  return NextResponse.json(user, { status: 201 });
}

ServerActions를 사용하면 폼제출 같은 서버 로직을 컴포넌트 안에서 직접 정의할 수 있다.

async function ContactForm() {
  async function submitForm(formData: FormData) {
    'use server';
    const email = formData.get('email');
    await db.contact.create({ data: { email } });
  }

  return (
    <form action={submitForm}>
      <input name="email" type="email" />
      <button type="submit">제출</button>
    </form>
  );
}

성능 최적화

React만 사용하면 이미지 최적화, 코드 스플리팅, 폰트 최적화 등을 직접 설정해야한다. Next.js는 기본적으로 내장하고 있다.

  • next/image : 자동 이미지 리사이징, WebP변환, lazy loading
  • next/font : 구글 폰트를 빌드 시 다운로드해 외부 네트워크 요청 제거
  • next/link : 뷰포트에 보이는 링크의 페이지를 prefetch
  • 자동 코드 스플리팅 : 페이지 단위로 JS를 분리해 필요한 것만 로드